﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Concat
{
    abstract class Term
    {
        public Location loc;

        public Term(Location loc)
        {
            this.loc = loc;
        }

        public abstract HashSet<string> closureVars(HashSet<string> vars);
    }

    class LiteralTerm : Term
    {
        public LiteralTerm(Location loc)
            : base(loc)
        {
        }

        public override HashSet<string> closureVars(HashSet<string> vars)
        {
            return new HashSet<string>();
        }
    }
    class IntTerm : LiteralTerm
    {
        public long value;

        public IntTerm(long value, Location loc)
            : base(loc)
        {
            this.value = value;
        }

        public override string ToString()
        {
            return value.ToString();
        }
    }
    class NumTerm : LiteralTerm
    {
        public double value;

        public NumTerm(double value, Location loc)
            : base(loc)
        {
            this.value = value;
        }

        public override string ToString()
        {
            return value.ToString();
        }
    }
    class StrTerm : LiteralTerm
    {
        private string text;
        public string data;

        public StrTerm(string text, string data, Location loc)
            : base(loc)
        {
            this.text = text;
            this.data = data;
        }

        public override string ToString()
        {
            return text;
        }
    }
    class SymTerm : LiteralTerm
    {
        public string symbol;

        public SymTerm(string symbol, Location loc)
            : base(loc)
        {
            this.symbol = symbol;
        }

        public override string ToString()
        {
            return ":" + symbol;
        }
    }
    class ObjTerm : LiteralTerm
    {
        public object obj;

        public ObjTerm(object obj, Location loc)
            : base(loc)
        {
            this.obj = obj;
        }

        public override string ToString()
        {
            return obj.ToString();
        }
    }

    class QuoteTerm : LiteralTerm
    {
        public string[] formals;
        public Term[] terms;

        public QuoteTerm(Location loc, string[] formals, params Term[] terms)
            : base(loc)
        {
            this.formals = formals;
            this.terms = terms;
        }

        public static string str(string[] formals, params Term[] terms)
        {
            return "["
                + (formals == null
                  ? "(" + formals.interleave(" ").concat() + ") "
                  : "")
                 + terms.map(word => word.ToString())
                        .interleave(" ")
                        .concat()
                 + "]";
        }

        public override string ToString()
        {
            return QuoteTerm.str(formals, terms);
        }

        public override HashSet<string> closureVars(HashSet<string> vars)
        {
            return terms.map(term => term.closureVars(vars)).combine();
        }
    }

    class IdentTerm : Term
    {
        public string ident;

        public IdentTerm(string ident, Location loc)
            : base(loc)
        {
            this.ident = ident;
        }

        public override string ToString()
        {
            return ident;
        }

        public override HashSet<string> closureVars(HashSet<string> vars)
        {
            if (vars.Contains(ident)) return new HashSet<string>() { ident };
            return new HashSet<string>();
        }
    }

    class DefTerm : Term
    {
        public string[] formals;
        public string name;
        public Location bodyLoc;
        public Term[] def;

        public DefTerm(string[] formals, string name, Location bodyLoc, Location loc, params Term[] def)
            : base(loc)
        {
            this.formals = formals ?? new string[0];
            this.name = name;
            this.bodyLoc = bodyLoc;
            this.def = def;
        }
        public DefTerm(string[] formals, string name, Location loc, QuoteTerm quote)
            : this(formals, name, quote.loc, loc, quote.terms)
        {
        }

        public override string ToString()
        {
            string str = "def ";
            if (formals.Length != 0)
                str += "(" + formals.interleave(" ").concat() + ") ";
            return str + name + " " + QuoteTerm.str(null, def).ToString();
        }

        public override HashSet<string> closureVars(HashSet<string> vars)
        {
            return def.map(term => term.closureVars(vars)).combine();
        }
    }

    class DefMethodTerm : Term
    {
        public string[] formals;
        public string[] types;
        public string[] paramNames;
        public string name;
        public Location bodyLoc;
        public Term[] def;

        public DefMethodTerm(string[] formals, string[] types, string[] paramNames, string name,
            Location bodyLoc, Location loc, params Term[] def)
            : base(loc)
        {
            this.formals = formals;
            this.types = types;
            this.paramNames = paramNames;
            this.name = name;
            this.bodyLoc = bodyLoc;
            this.def = def;
        }
        public DefMethodTerm(string[] formals, string[] types, string[] paramNames, string name,
            Location loc, QuoteTerm quote)
            : this(formals, types, paramNames, name, quote.loc, loc, quote.terms)
        {
        }

        public override HashSet<string> closureVars(HashSet<string> vars)
        {
            return def.map(term => term.closureVars(vars)).combine();
        }
    }
    class StructTerm : Term
    {
        public string name;
        public string[] slots;

        public StructTerm(Location loc, string name, params string[] slots)
            : base(loc)
        {
            this.name = name;
            this.slots = slots;
        }

        public override HashSet<string> closureVars(HashSet<string> vars)
        {
            return new HashSet<string>();
        }
    }
    class ExtendStructTerm : Term
    {
        public string parent, name;
        public string[] slots;

        public ExtendStructTerm(Location loc, string parent, string name, params string[] slots)
            : base(loc)
        {
            this.parent = parent;
            this.name = name;
            this.slots = slots;
        }

        public override HashSet<string> closureVars(HashSet<string> vars)
        {
            return new HashSet<string>();
        }
    }
    class ClassTerm : Term
    {
        public string name;
        public DefMethodTerm[] protocol;

        public ClassTerm(Location loc, string name, params DefMethodTerm[] protocol)
            : base(loc)
        {
            this.name = name;
            this.protocol = protocol;
        }

        public override HashSet<string> closureVars(HashSet<string> vars)
        {
            return protocol.map(method => method.def.map(term => term.closureVars(vars)).combine()).combine();
        }
    }
    class ExtendClassTerm : Term
    {
        public string parent, name;
        public DefMethodTerm[] protocol;

        public ExtendClassTerm(Location loc, string parent, string name, params DefMethodTerm[] protocol)
            : base(loc)
        {
            this.parent = parent;
            this.name = name;
            this.protocol = protocol;
        }

        public override HashSet<string> closureVars(HashSet<string> vars)
        {
            return protocol.map(method => method.def.map(term => term.closureVars(vars)).combine()).combine();
        }
    }
    class ClassMemberTerm : Term
    {
        public string className;
        public string[] memberNames;

        public ClassMemberTerm(Location loc, string className, params string[] memberNames)
            : base(loc)
        {
            this.className = className;
            this.memberNames = memberNames;
        }

        public override HashSet<string> closureVars(HashSet<string> vars)
        {
            return new HashSet<string>();
        }
    }
}
